//
//  ZYUrlSessionManager.m
//  ZYWebImage
//
//  Created by wangzhipan on 2025/4/11.
//

#import "ZYUrlSessionManager.h"
#import "ZYJSONResponseSerializer.h"
#import "ZYUrlSessionManagerTaskDelegate.h"
#import <objc/runtime.h>

@interface ZYUrlSessionManager() <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (nonatomic, strong, readwrite) NSURLSession *session;
@property (nonatomic, strong, readwrite) NSOperationQueue *queue;
@property (nonatomic, strong, nullable) NSURLSessionConfiguration *sessionConfiguration;
@property (nonatomic, strong, nullable) NSMutableDictionary *taskIdByTaskDelegatesDict;
@property (nonatomic, strong, nullable) NSLock *lock;
@end

@implementation ZYUrlSessionManager

- (instancetype)init
{
    return [self initWithSessionConfiguration:nil];
}

- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration
{
    if (self = [super init]) {
        self.queue = [[NSOperationQueue alloc] init];
        /*
         queue维护了多个字典和数组来跟踪任务、进度和回调。这些数据结构需要在多个回调和委托方法中被访问和修改。通过限制操作队列的并发数为 1， 实际上创建了一个串行队列，确保所有对共享资源的访问都是顺序执行的，避免了多线程并发访问可能导致的数据竞争和不一致问题。
         
         这个设置只影响 URLSessionManager 内部用于处理委托回调的队列，不会限制网络请求本身的并发数。
         网络请求的并发数由 NSURLSessionConfiguration 的 HTTPMaximumConnectionsPerHost 属性控制，默认情况下允许多个并发连接
         */
        self.queue.maxConcurrentOperationCount = 1;
        
        self.sessionConfiguration = sessionConfiguration;
        if (!sessionConfiguration) {
            self.sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
        }
        
        //注意，这里持有的delegate是retain强引用
        self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration
                                                     delegate:self
                                                delegateQueue:self.queue];
        
        self.responseSerializer = [ZYJSONResponseSerializer serializer];
        self.securityPolicy = [ZYSecurityPolicy defaultPolicy];
        
        self.reachabilityManager = [AFNetworkReachabilityManager manager];
        
        //一个task对应一个delegate，将操作全部转入delegate单独处理
        self.taskIdByTaskDelegatesDict = [NSMutableDictionary dictionary];
        self.lock = [NSLock new];
        self.lock.name = @"ZYUrlSessionManager_Lock";
        
        [self.session getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
            for (NSURLSessionDataTask *dataTask in dataTasks) {
                [self p_addDelegateForDataTask:dataTask completedBlock:nil];
            }
            
            for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
                [self p_addDelegateForUplaodTask:uploadTask uploadProgress:nil completedBlock:nil];
            }
        }];
    }
    return self;
}

- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks resetSession:(BOOL)resetSession
{
    //NSURLSession一定需要invalidate，否则会造成内存泄露
    if (cancelPendingTasks) {
        [self.session invalidateAndCancel];
    }
    else {
        [self.session finishTasksAndInvalidate];
    }
    
    if (resetSession) {
        self.session = nil;
    }
}

//get\post\head请求
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               completedBlock:(nullable void(^)(NSURLResponse *response, id _Nullable rspObj, NSError * _Nullable error))completedBlock
{
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
    [self p_addDelegateForDataTask:dataTask completedBlock:completedBlock];
    return dataTask;
}

//小数据上传
- (NSURLSessionDataTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                   uploadProgress:(nullable void(^)(NSUInteger currentSentSize, NSUInteger totalSize))uploadProgress
                                   completedBlock:(nullable void(^)(NSURLResponse *response, id _Nullable rspObj, NSError * _Nullable error))completedBlock
{
    NSURLSessionDataTask *uploadTask = [self.session dataTaskWithRequest:request];
    [self p_addDelegateForUplaodTask:uploadTask
                      uploadProgress:uploadProgress
                      completedBlock:completedBlock];
    return uploadTask;
}

//大数据上传
- (NSURLSessionDataTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
                                         uploadProgress:(nullable void(^)(NSUInteger currentSentSize, NSUInteger totalSize))uploadProgress
                                         completedBlock:(nullable void(^)(NSURLResponse *response, id _Nullable rspObj, NSError * _Nullable error))completedBlock
{
    NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithStreamedRequest:request];
    [self p_addDelegateForUplaodTask:uploadTask
                      uploadProgress:uploadProgress
                      completedBlock:completedBlock];
    return uploadTask;
}

#pragma mark - add delegate for Task
- (void)p_addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                  completedBlock:(nullable void(^)(NSURLResponse *response, id _Nullable rspObj, NSError * _Nullable error))completedBlock
{
    ZYUrlSessionManagerTaskDelegate *delegate = [[ZYUrlSessionManagerTaskDelegate alloc] initWithSessionManager:self];
    delegate.completedBlock = completedBlock;
    [self p_setDelegate:delegate forTask:dataTask];
}

- (void)p_addDelegateForUplaodTask:(NSURLSessionDataTask *)uploadTask
                    uploadProgress:(nullable void(^)(NSUInteger currentSentSize, NSUInteger totalSize))uploadProgress
                    completedBlock:(nullable void(^)(NSURLResponse *response, id _Nullable rspObj, NSError * _Nullable error))completedBlock
{
    ZYUrlSessionManagerTaskDelegate *delegate = [[ZYUrlSessionManagerTaskDelegate alloc] initWithSessionManager:self];
    delegate.uploadProgress = uploadProgress;
    delegate.completedBlock = completedBlock;
    [self p_setDelegate:delegate forTask:uploadTask];
}

#pragma mark - set Task and delegate for taskIdByTaskDelegatesDict
- (void)p_setDelegate:(ZYUrlSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task
{
    NSAssert(delegate != nil, @"p_setDelegate delegate is nil");
    NSAssert(task != nil, @"p_setDelegate task is nil");
    if (!delegate || !task) {
        return;
    }
    [self.lock lock];
    self.taskIdByTaskDelegatesDict[@(task.taskIdentifier)] = delegate;
    [self.lock unlock];
}

- (ZYUrlSessionManagerTaskDelegate *)p_delegateForTask:(NSURLSessionTask *)task
{
    ZYUrlSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.taskIdByTaskDelegatesDict[@(task.taskIdentifier)];
    [self.lock unlock];
    return delegate;
}

- (void)p_removeDelegateForTask:(NSURLSessionTask *)task
{
    NSAssert(task != nil, @"p_removeDelegateForTask task is nil");
    if (!task) {
        return;
    }
    [self.lock lock];
    [self.taskIdByTaskDelegatesDict removeObjectForKey:@(task.taskIdentifier)];
    [self.lock unlock];
}

#pragma mark - NSURLSessionTaskDelegate\NSURLSessionDataDelegate
/*
 这个方法与 URLSession:didReceiveChallenge:completionHandler: 的主要区别是：

 - 会话级别的挑战处理适用于整个会话的所有请求
 - 任务级别的挑战处理只适用于特定的请求任务
 - 如果两者都实现了，任务级别的处理优先级更高
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
    
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    NSURLCredential *credential = nil;
    
    BOOL evaluateServerTrust = [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
    if (evaluateServerTrust) {
        //进行securityPolicy验证
        if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
            disposition = NSURLSessionAuthChallengeUseCredential;
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        } else {
            objc_setAssociatedObject(task, &kAuthenticationChallengeErrorKey,
                                     [self p_serverTrustErrorForServerTrust:challenge.protectionSpace.serverTrust url:task.currentRequest.URL],
                                     OBJC_ASSOCIATION_RETAIN);
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

- (void)URLSession:(NSURLSession *)session 
              task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream * _Nullable))completionHandler
{
    NSInputStream *inputStream = nil;
    if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
        inputStream = [task.originalRequest.HTTPBodyStream copy];
    }
    completionHandler(inputStream);
}

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
    ZYUrlSessionManagerTaskDelegate *delegate = [self p_delegateForTask:task];
    if (delegate) {
        [delegate URLSession:session
                        task:task
             didSendBodyData:bytesSent
              totalBytesSent:totalBytesSent
    totalBytesExpectedToSend:totalBytesExpectedToSend];
    }
}

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    ZYUrlSessionManagerTaskDelegate *delegate = [self p_delegateForTask:task];
    if (delegate) {
        [delegate URLSession:session
                        task:task
        didCompleteWithError:error];
        [self p_removeDelegateForTask:task];
    }
}

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
{
    ZYUrlSessionManagerTaskDelegate *delegate = [self p_delegateForTask:task];
    if (delegate) {
        [delegate URLSession:session
                        task:task
  didFinishCollectingMetrics:metrics];
    }
}

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    ZYUrlSessionManagerTaskDelegate *delegate = [self p_delegateForTask:dataTask];
    if (delegate) {
        [delegate URLSession:session
                    dataTask:dataTask
              didReceiveData:data];
    }
}


#pragma mark - private other
- (nonnull NSError *)p_serverTrustErrorForServerTrust:(nullable SecTrustRef)serverTrust url:(nullable NSURL *)url
{
    NSBundle *CFNetworkBundle = [NSBundle bundleWithIdentifier:@"com.apple.CFNetwork"];
    NSString *defaultValue = @"The certificate for this server is invalid. You might be connecting to a server that is pretending to be “%@” which could put your confidential information at risk.";
    NSString *descriptionFormat = NSLocalizedStringWithDefaultValue(@"Err-1202.w", nil, CFNetworkBundle, defaultValue, @"") ?: defaultValue;
    NSString *localizedDescription = [descriptionFormat componentsSeparatedByString:@"%@"].count <= 2 ? [NSString localizedStringWithFormat:descriptionFormat, url.host] : descriptionFormat;
    NSMutableDictionary *userInfo = [@{
        NSLocalizedDescriptionKey: localizedDescription
    } mutableCopy];

    if (serverTrust) {
        userInfo[NSURLErrorFailingURLPeerTrustErrorKey] = (__bridge id)serverTrust;
    }

    if (url) {
        userInfo[NSURLErrorFailingURLErrorKey] = url;

        if (url.absoluteString) {
            userInfo[NSURLErrorFailingURLStringErrorKey] = url.absoluteString;
        }
    }

    return [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorServerCertificateUntrusted userInfo:userInfo];
}

#pragma mark - getter && setter

- (NSArray *)p_tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    //getTasksWithCompletionHandler 回调是异步执行的
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return tasks;
}

- (NSArray *)tasks {
    return [self p_tasksForKeyPath:NSStringFromSelector(_cmd)];
}

- (NSArray *)dataTasks {
    return [self p_tasksForKeyPath:NSStringFromSelector(_cmd)];
}

- (NSArray *)uploadTasks {
    return [self p_tasksForKeyPath:NSStringFromSelector(_cmd)];
}
@end
